﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Gibbed.IO;
using AmaroK86.MassEffect3.ZlibBlock;
using AmaroK86.MassEffect3.UnrealObjects;

namespace AmaroK86.MassEffect3.Image
{
    public static class ImageHandler
    {
        public static string showPreview(Texture2D tex2D, ImageSize selectedSize)
        {
            string previewFileName = "preview.dds";
            string previewTgaFileName = "preview00.tga";

            if (File.Exists(previewTgaFileName))
                File.Delete(previewTgaFileName);

            ImageFile imgFile = extractImage(tex2D, selectedSize, false);

            if (imgFile is DDS)
            {
                // covert dds file into tga
                imgFile.saveToFile(previewFileName);
                ExecuteCommandSync("readdxt " + previewFileName);
                if (File.Exists(previewFileName))
                    File.Delete(previewFileName);
            }
            else if (imgFile is TGA)
                imgFile.saveToFile(previewTgaFileName);
            else
                throw new InvalidDataException("Unable to recognize ImageFile object, not DDS or TGA");

            return previewTgaFileName;
        }

        private static void ExecuteCommandSync(object command)
        {
            try
            {
                // create the ProcessStartInfo using "cmd" as the program to be run,
                // and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows,
                // and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo =
                    new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

                // The following commands are needed to redirect the standard output.
                // This means that it will be redirected to the Process.StandardOutput StreamReader.
                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                // Display the command output.
                Console.WriteLine(result);
            }
            catch
            {
                // Log the exception
            }
        }

        public static ImageFile extractImage(Texture2D tex2D, ImageSize selectedSize, bool forceMipMaps = true)
        {
            string archivePath = null;

            //if it has a texturecache file, it searches on same pcc folder
            if (tex2D.properties.exist("TextureFileCacheName"))
                archivePath = Path.GetDirectoryName(tex2D.pccRef.pccFileName) + "\\" + (string)tex2D.properties["TextureFileCacheName"].Value + ".tfc";

            ImageFile imageFile;

            switch (tex2D.Extension)
            {
                case "dds":
                    imageFile = new DDS(tex2D, archivePath, selectedSize, forceMipMaps);
                    break;
                case "tga":
                    imageFile = new TGA(tex2D, archivePath, selectedSize);
                    break;
                default:
                    throw new InvalidDataException("Invalid image format: " + (string)tex2D.properties["Format"].Value);
            }

            return imageFile;
        }

        public static Texture2D.ImageList buildImgList(string imgPathFile, Texture2D tex2D, string newArchiveFileName = null, byte[] newGUID = null)
        {
            string archiveFileName;

            if (newArchiveFileName != null && newArchiveFileName != "")
            {
                archiveFileName = Path.GetDirectoryName(tex2D.pccRef.pccFileName) + "\\" + newArchiveFileName + ".tfc";

                if (!File.Exists(archiveFileName))
                {
                    using (FileStream archiveStream = File.Create(archiveFileName))
                    {
                        archiveStream.WriteBytes(newGUID);
                    }
                }
            }
            else
                archiveFileName = Path.GetDirectoryName(tex2D.pccRef.pccFileName) + "\\" + tex2D.archiveName + ".tfc";

            Texture2D.ImageList imgList = new Texture2D.ImageList();
            string fileExtension = Path.GetExtension(imgPathFile);
            string fileName = Path.GetFileNameWithoutExtension(imgPathFile);
            ImageFile imageMipMap;

            switch (fileExtension)
            {
                case ".dds":
                    imageMipMap = new DDS(imgPathFile);
                    if (tex2D.imgList.Count > 1 && !(imageMipMap as DDS).hasMipMaps)
                    {
                        throw new FormatException("The image doesn't have any mipmaps");
                    }
                    break;
                case ".tga":
                    imageMipMap = new TGA(imgPathFile);
                    break;
                default: throw new FormatException("Invalid image format");
            }
            //Console.WriteLine("Image Format: {0}", imageMipMap.format);

            //check if image has mipmaps
            /*using (FileStream imageStream = File.OpenRead(imgPathFile))
            {
                //if (imageStream.Length - headerSize != ImageMipMapDataSize(imageMipMap.imgSize, imageMipMap.format, imageMipMap.BPP))
                if (imageStream.Length - headerSize != ImageMipMapDataSize(imageMipMap.imgSize, imageMipMap.format, imageMipMap.BPP))
                {
                    throw new FormatException("The image doesn't have any mipmaps");
                }
                //Console.WriteLine("bytes in file: {0}, bytes calulated: {1}, BPP: {2}", imageStream.Length - headerSize, ImageMipMapDataSize(imageMipMap.imgSize, imageMipMap.format, imageMipMap.BPP), imageMipMap.BPP);
            }*/

            byte[] buffer = null;

            // add the first tga image
            if (fileExtension == ".tga")
            {
                //buffer = new byte[imageMipMap.imgData.Length];
                buffer = imageMipMap.imgData;
                //imageList.Add(imageMipMap);
            }

            int maxCount = (int)Math.Min(imageMipMap.imgSize.width, imageMipMap.imgSize.height);
            int count = 1;
            int imgDataPos = 0;
            while (count <= maxCount)
            {
                ImageInfo imgInfo = new ImageInfo();
                ImageSize newImageSize = imageMipMap.imgSize / count;

                imgInfo.imgSize = newImageSize;

                //if (newImageSize.width <= 4 && newImageSize.height <= 4)
                //    break;

                // image size of current image (without mipmaps)
                int imgDataSize = (int)ImageDataSize(newImageSize, imageMipMap.format, imageMipMap.BPP);

                if (fileExtension == ".dds")
                {
                    buffer = new byte[imgDataSize];
                    Buffer.BlockCopy(imageMipMap.imgData, imgDataPos, buffer, 0, imgDataSize);
                    imgDataPos += imgDataSize;

                }

                if (tex2D.imgList.Any(image => image.storageType == ImageInfo.storage.arcCpr || image.storageType == ImageInfo.storage.arcUnc) && (newImageSize.width > 32 && newImageSize.height > 32))
                {
                    using (FileStream archiveStream = File.OpenWrite(archiveFileName))
                    {
                        archiveStream.Seek(0, SeekOrigin.End);
                        imgInfo.offset = (int)archiveStream.Position;
                        imgInfo.pccImage = null;
                        imgInfo.uncSize = buffer.Length;

                        if (tex2D.imgList.Any(image => image.storageType == ImageInfo.storage.arcCpr))
                        {
                            imgInfo.storageType = ImageInfo.storage.arcCpr;
                            byte[] cprBuffer = ZBlock.Compress(buffer);
                            archiveStream.WriteBytes(cprBuffer);
                            imgInfo.cprSize = cprBuffer.Length;
                        }
                        else
                        {
                            imgInfo.storageType = ImageInfo.storage.arcUnc;
                            archiveStream.WriteBytes(buffer);
                            imgInfo.cprSize = buffer.Length;
                        }
                    }
                }
                else
                {
                    imgInfo.storageType = ImageInfo.storage.pccSto;
                    imgInfo.offset = 0;
                    imgInfo.pccImage = buffer;
                    imgInfo.uncSize = buffer.Length;
                    imgInfo.cprSize = buffer.Length;
                }

                imgList.Add(imgInfo);

                count *= 2;

                if (fileExtension == ".tga")
                {
                    if (newImageSize.width != 1 && newImageSize.height != 1)
                        buffer = ShrinkImage(newImageSize, buffer, imageMipMap.BPP);
                }

                if (tex2D.imgList.Count == 1)
                    break;
            }

            return imgList;
        }

        private static long ImageDataSize(ImageSize imgsize, string format, float BytesPerPixel)
        {
            long totalBytes = (long)((float)(imgsize.width * imgsize.height) * BytesPerPixel);
            switch (format)
            {
                case "PF_DXT1":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 8;
                    else
                        return totalBytes;
                case "PF_DXT5":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 16;
                    else
                        return totalBytes;
                case "PF_V8U8":
                case "PF_G8":
                case "PF_A8R8G8B8":
                    return totalBytes;
                default:
                    throw new FormatException("Invalid image format");
            }
        }
        /*
                private static long ImageMipMapDataSize(ImageSize imgsize, string format, float BytesPerPixel)
                {
                    long totalBytes = (long)((float)(imgsize.width * imgsize.height) * BytesPerPixel);
                    //Console.WriteLine("width: {0}, height: {1}, BPP: {2:F}, totalBytes: {3}", imgsize.width, imgsize.height, BytesPerPixel, totalBytes);
                    switch (format)
                    {
                        case "PF_DXT1":
                            if (imgsize.width <= 4 && imgsize.height <= 4)
                                return 24;
                            else
                                return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                        case "PF_DXT5":
                            if (imgsize.width <= 4 && imgsize.height <= 4)
                                return 48;
                            else
                                return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                        case "PF_V8U8":
                            if (imgsize.width <= 1 && imgsize.height <= 1)
                                return 2;
                            else
                                return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                        case "PF_G8":
                        case "PF_A8R8G8B8":
                            return totalBytes;
                        default:
                            throw new FormatException("Invalid image format");
                    }
                }
        */
        private static byte[] ShrinkImage(ImageSize imgsize, byte[] imageData, float BytesPerPixel)
        {
            byte[] final = new byte[imageData.Length / 4];
            int BPP = (int)BytesPerPixel;

            for (int i = 0; i < imgsize.height; i += 2)
            {
                for (int j = 0; j < imgsize.width; j += 2)
                {
                    for (int k = 0; k < BPP; k++)
                    {
                        final[(i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k] = (byte)
                          (((int)imageData[i * (imgsize.width * BPP) + j * BPP + k] +
                            (int)imageData[i * (imgsize.width * BPP) + j * BPP + BPP + k] +
                            (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + k] +
                            (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + BPP + k]) / 4);
                        /*if(imgsize == new ImageSize(8,8))
                            Console.WriteLine("pos {5}: {0} = ({1} + {2} + {3} + {4}) / 4 ",
                                final[(i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k],
                                (int)imageData[i * (imgsize.width * BPP) + j * BPP + k],
                                (int)imageData[i * (imgsize.width * BPP) + j * BPP + BPP + k],
                                (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + k],
                                (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + BPP + k],
                                (i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k);*/
                    }
                }
            }

            return final;
        }

    }
}
